package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.config.WebSocketServer;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.dto.AlertRuleDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.service.AlertRuleService;
import com.zzyl.utils.ObjectUtil;
import com.zzyl.utils.StringUtils;
import com.zzyl.vo.AlertDataVo;
import com.zzyl.vo.AlertRuleVo;
import com.zzyl.vo.DeviceDataVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AlertRuleServiceImpl implements AlertRuleService {

    @Autowired
    private AlertRuleMapper alertRuleMapper;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Value(value = "${zzyl.alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;

    @Value(value = "${zzyl.alert.managerRole}")
    private String managerRole;

    @Autowired
    private AlertDataMapper alertDataMapper;

    @Override
    public void createAlertRule(AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.createAlertRule(alertRule);
    }

    @Override
    public PageResponse getPage(Integer pageNum, Integer pageSize, String alertRuleName, String functionName, String productKey) {
        PageHelper.startPage(pageNum, pageSize);
        Page<AlertRuleVo> alertRuleVoPage = alertRuleMapper.getPage(alertRuleName,functionName,productKey);
      alertRuleVoPage.getResult().forEach(alertRuleVo -> {
          String format = CharSequenceUtil.format("{}{}{},持续{}周期报警", alertRuleVo.getFunctionName(), alertRuleVo.getOperator(), alertRuleVo.getValue(), alertRuleVo.getDuration());
          alertRuleVo.setRules(format);
      });
      return PageResponse.of(alertRuleVoPage,AlertRuleVo.class);
    }

    @Override
    public AlertRuleVo getAlertRuleVo(Long id) {
        return alertRuleMapper.getAlertRuleVo(id);
    }

    @Override
    public void updateAlertRuleVo(AlertRuleDto alertRuleDto, Long id) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRule.setId(id);
        alertRuleMapper.updateAlertRuleVo(alertRule);
    }

    @Override
    public void deleteAlertRuleVo(Long id) {
        alertRuleMapper.deleteAlertRuleVo(id);
    }

    @Override
    public void updateStatus(Long id, Integer status) {
        AlertRule alertRule = new AlertRule();
        alertRule.setId(id);
        alertRule.setStatus(status);
        alertRuleMapper.updateAlertRuleVo(alertRule);
    }

    @Override
    public List<AlertRuleVo> getAlertRuleVoAll(String functionId, String iotId, String productKey) {
        List<AlertRuleVo> alertRuleVos = alertRuleMapper.getAlertRuleVoAll(functionId,iotId,productKey);
        return alertRuleVos;
    }

    @Override
    public void AlertRuleDeviceDateCheck(DeviceDataVo deviceDataVo) {
        //1.判断时间是否在1分钟内
        LocalDateTime alarmTime = deviceDataVo.getAlarmTime();//获取数据上报时间
        //调用时间工具类的between类的方法获取两个时间的差值  并指定时间单位
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.SECONDS);
        if (between > 60) { //判断差值时间是否大于60秒一分钟 如果大于直接返回
            return;
        }
        //准备对应报警规则数据 规则类型有指定产品下的单个设备 和指定产品下的所有设备
        //先查询产品下的所有设备的报警规则
        List<AlertRuleVo> AllAlertRuleVo = alertRuleMapper.getAlertRuleVoAll(deviceDataVo.getFunctionId(), "-1", deviceDataVo.getProductKey());
        //再查询该产品下的指定单个设备的报警规则
        List<AlertRuleVo> theAlertRuleVoAll = alertRuleMapper.getAlertRuleVoAll(deviceDataVo.getFunctionId(), deviceDataVo.getIotId(), deviceDataVo.getProductKey());
        //将两个规则的集合合并 得到所有规则
        Collection<AlertRuleVo> alertRuleVos = CollUtil.addAll(AllAlertRuleVo, theAlertRuleVoAll);
        //进行规则判空 如果为空结束 不为空则继续执行
        if (CollUtil.isEmpty(alertRuleVos)){
            return;
        }
        //循环遍历每一条规则进行校验 参数为 设备上报数据 和 报警规则
        alertRuleVos.forEach(alertRuleVo -> deviceDateAlarmHandler(deviceDataVo,alertRuleVo));
    }

    private void deviceDateAlarmHandler(DeviceDataVo deviceDataVo, AlertRuleVo alertRuleVo) {
        //第一层校验 判断是否在生效时间内 获取生效时间
        String[] split = alertRuleVo.getAlertEffectivePeriod().split("~");//告警生效时间
        LocalTime startTime = LocalTime.parse(split[0]);//将字符串转换为时间格式
        LocalTime endTime = LocalTime.parse(split[1]);
        //获取数据上报时间 获取数据上报时间并转换成localTime
        LocalTime alertTime = LocalDateTimeUtil.of(deviceDataVo.getAlarmTime()).toLocalTime();
        //判断生效时间 是否在上报时间之后 或者 结束时间在上报时间之前 如果符合则代表 该数据不在生效时间内直接返回结束
        if (startTime.isAfter(alertTime) || endTime.isBefore(alertTime)) {
            return;
        }
        //获取设备iotid
        String iotId = deviceDataVo.getIotId();//组装key方便redis获取和存储
        String aggCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX+iotId + ":" + deviceDataVo.getFunctionId()+":"+alertRuleVo.getId();
        //第二层校验 判断数据是否达到阈值
        //获取设备上报数据的数值 和告警规则的阈值进行比较
        //调用工具类 进行比较 如果返回设备上报数据大于规则阈值 则代表为异常数据
        int compare = NumberUtil.compare(Double.valueOf(deviceDataVo.getDataValue()), alertRuleVo.getValue());
        //判断返回值是否大于等于0 且 规则的运算符 等于>=
        //判断返回值是否小于0 且规则运算符是< 符合则是异常数据
        if (compare >= 0 && alertRuleVo.getOperator().equals(">=")||
        compare < 0 && alertRuleVo.getOperator().equals("<")){
            log.info("设备上传数据符合异常规则，达到阈值");
        }else {//如果不符合需要删除redsi中的告警次数 并返回
            redisTemplate.delete(aggCountKey);
            return;
        }


        //第三层校验 判断沉默周期 如果不为空表示在沉默周期内 直接返回进行下一次循环

        String silentCacheKey = CacheConstants.ALERT_SILENT_PREFIX +iotId + ":" + deviceDataVo.getFunctionId()+":"+alertRuleVo.getId();
        String silentData = redisTemplate.opsForValue().get(silentCacheKey);
        if (StringUtils.isNotEmpty(silentData)){
            return;
        }
        //第四层校验 判断持续周期
        //1.redis中查询已报警次数

        String aggCount = redisTemplate.opsForValue().get(aggCountKey);
        //计算当前报警次数
        int Count = StringUtils.isEmpty(aggCount) ? 1 : Integer.parseInt(aggCount) + 1;
        //判断报警次数是否等于持续周期
        if (ObjectUtil.notEqual(Count,alertRuleVo.getDuration())){
            redisTemplate.opsForValue().set(aggCountKey,Count+"");
            return;
        }
        //删除redis报警树
        redisTemplate.delete(aggCountKey);
        //添加redis沉默周期 并设置过期时间
        redisTemplate.opsForValue().set(silentCacheKey,deviceDataVo.getDataValue(),alertRuleVo.getAlertSilentPeriod(), TimeUnit.MINUTES);

        //判断是老人异常数据还是设备异常数据
        List<Long> customerIds = new ArrayList<>();
        if (alertRuleVo.getAlertDataType().equals(0)){ //如果等于0则代表为老人异常数据 不等于0为设备异常数据
            //老人异常数据 是否是老人随身设备 找对应的护理员
           if (deviceDataVo.getLocationType().equals(0)){//如果为0则代表是随身设备 需要通知对应的护理员 查询对应护理员id
               customerIds = deviceMapper.selectNursingIdsByiotIdwithelder(iotId);//如果设备位置类型为1 且 物理位置为2 则代表是床位设备
           }else if (deviceDataVo.getLocationType().equals(1) && deviceDataVo.getPhysicalLocationType().equals(2)){
               //通过床位找到老人再找到对应的护理员id
               customerIds = deviceMapper.selectNursingIdsByiotIdwithbed(iotId);
           }
        }else {
            //如果是设备异常数据  需要通知对应的维修员 调用mapper查询对应的维修员id
            customerIds = userRoleMapper.selectUserIDByRolename(deviceMaintainerRole);
        }

        //不管什么情况 都要通知超级管理员 所有还要查询超级管理员
        List<Long> managerIds = userRoleMapper.selectUserIDByRolename(managerRole);
        //将获取到的需要通知的员工的id合并进行去重
        List<Long> ids = CollUtil.addAllIfNotContains(managerIds, customerIds);
        ids = ids.stream().distinct().collect(Collectors.toList());
        //ids = CollUtil.distinct(ids);
        //最后批量保存告警数据
        List<AlertData> alertData = insertAlertData(ids, alertRuleVo, deviceDataVo);

        List<Long> userIds = alertData.stream().map(AlertData::getUserId).collect(Collectors.toList());
        websocketNotify(alertRuleVo,alertData.get(0),userIds);
    }

    @Autowired
    private WebSocketServer websocketServer;
    /**
     * 推送告警信息
     * @param alertRuleVo
     * @param alertData
     * @param userIds
     */
    private void websocketNotify(AlertRuleVo alertRuleVo, AlertData alertData, List<Long> userIds) {
        AlertNotifyDto alertNotifyDto = BeanUtil.toBean(alertData, AlertNotifyDto.class);
        alertNotifyDto.setNotifyType(1);
        alertNotifyDto.setFunctionName(alertRuleVo.getFunctionName());
        alertNotifyDto.setAlertDataType(alertData.getType());

        websocketServer.sendMessageToConsumer(alertNotifyDto,userIds);
    }




    private List<AlertData> insertAlertData(List<Long> ids, AlertRuleVo alertRuleVo, DeviceDataVo deviceDataVo) {
        AlertData alertData = BeanUtil.toBean(deviceDataVo, AlertData.class);
        alertData.setAlertRuleId(alertRuleVo.getId());
        String format = CharSequenceUtil.format("{}{}{},持续{}周期报警", alertRuleVo.getFunctionName(), alertRuleVo.getOperator(), alertRuleVo.getValue(), alertRuleVo.getDuration());
        alertData.setAlertReason(format);
        alertData.setStatus(0);
        alertData.setType(alertRuleVo.getAlertDataType());

        List<AlertData> dataList = ids.stream().map(id -> {
            AlertData dbAlertdata = BeanUtil.toBean(alertData, AlertData.class);
            dbAlertdata.setUserId(id);
            return dbAlertdata;
        }).collect(Collectors.toList());
        //批量插入
        alertDataMapper.batchInsert(dataList);
        return dataList;
    }
}
